package org.mmtk.policy.compressor;

import org.mmtk.plan.TransitiveClosure;
import org.mmtk.plan.compressor.Compressor;
import org.mmtk.plan.compressor.CompressorCollector;

import org.mmtk.policy.Space;

import org.mmtk.utility.Log;
import org.mmtk.utility.alloc.Allocator;
import org.mmtk.utility.alloc.BumpPointer;
import org.mmtk.utility.alloc.LinearScan;

import org.mmtk.vm.VM;

import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.ObjectReference;

@Uninterruptible
public class CopyingScanner extends LinearScan {

  /**
   * Both the 'compact' and 'calculate' phases can be thought of as sweeping
   * a pair of cursors across a linked list of regions.  Each cursor requires
   * maintaining pointers to the current region, the current address and the end of
   * the region.  The regionCursor class maintains these 3 pointers, while the
   * subclasses ToCursor and FromCursor provide methods specific to the
   * read and write pointers.
   */
  @Uninterruptible
  private static class RegionCursor {

    /** Name of the cursor - for debugging messages */
    private final String name;

    /**
     * The current region, or zero if the cursor is invalid (eg after advancing
     * past the end of the current work list
     */
    protected Address region;

    /**
     * The limit of the current region. When reading a populated region, this is the
     * address of the last used byte.  When writing to a fresh region, this is the last
     * byte in the region.
     */
    protected Address limit;

    /** The current address */
    protected Address cursor;

    private boolean initialized;

    /**
     * @param name The name of the region - for debugging messages.
     */
    public RegionCursor(String name) {
      this.name = name;
      this.initialized = false;
    }

    /**
     * Hook to allow subclasses to initialize the cursor in different ways.
     *
     * @param region The region to be processed.
     */
    public void init(Address region, Address cursor) {
      //if (VM.VERIFY_ASSERTIONS) BumpPointer.checkRegionMetadata(region);
      this.region = region;
      //this.cursor = MarkCopyLocal.getDataStart(region);
      this.cursor = cursor;
      this.limit = MarkCopyLocal.getRegionLimit(region);
      this.initialized = true;
      if (VM.VERIFY_ASSERTIONS) assertCursorInBounds();
    }
    
    public void init(Address region) {
      //if (VM.VERIFY_ASSERTIONS) BumpPointer.checkRegionMetadata(region);
      this.region = region;
      this.cursor = MarkCopyLocal.getDataStart(region);
      this.limit = MarkCopyLocal.getRegionLimit(region);
      this.initialized = true;
      if (VM.VERIFY_ASSERTIONS) assertCursorInBounds();
    }
    
    public boolean isInit() {
      return initialized;
    }

    /**
     * Assert that the cursor is within the bounds of the region.  Calls to this
     * must be guarded by {@code if (VM.VERIFY_ASSERTIONS)}
     */
    protected void assertCursorInBounds() {
      VM.assertions._assert(!region.isZero());
      VM.assertions._assert(cursor.GE(BumpPointer.getDataStart(region)),
      "Cursor is below start of region");
      if(!cursor.LE(limit)) {
        Log.write("cursor: "); Log.write(cursor); Log.writeln();
        Log.write("limit: "); Log.write(limit); Log.writeln();
        VM.assertions._assert(false,"Cursor beyond end of region");
      }
        
    }

    /**
     * Increment the cursor.
     * @param size Bytes to increment by
     */
    public void inc(int size) {
      this.cursor = cursor.plus(size);
      if (VM.VERIFY_ASSERTIONS) assertCursorInBounds();
    }

    /**
     * Increment the cursor to a specific address
     * @param cursor Destination address
     */
    public void incTo(Address cursor) {
      if (VM.VERIFY_ASSERTIONS) assertCursorInBounds();
      if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(cursor.GE(this.cursor));
      this.cursor = cursor;
    }

    /**
     * @param other Other region
     * @return {@code true} if this cursor points to the same region as {@code other}
     */
    public boolean sameRegion(RegionCursor other) {
      return region.EQ(other.getRegion());
    }

    /**
     * @param size Size in bytes
     * @return {@code true} if {@code size} bytes are available in the current region
     */
    public boolean isAvailable(int size) {
      return cursor.plus(size).LE(limit);
    }

    /**
     * @return The current cursor
     */
    public Address get() {
      return cursor;
    }

    /**
     * @return The current region pointer
     */
    public Address getRegion() {
      return region;
    }

    /**
     * @return The current region limit
     */
    public Address getLimit() {
      return limit;
    }

    /**
     * Follow the linked-list of regions to the next region.
     */
    public void advanceToNextRegion() {
      Address nextRegion = MarkCopyLocal.getNextRegion(region);
      if (nextRegion.isZero()) {
        region = Address.zero();
        if(VM.VERIFY_ASSERTIONS) {
          VM.assertions._assert(false, "advanceToNextRegion does not have a next region");
        }
      } else {
        init(nextRegion);
        if (VM.VERIFY_ASSERTIONS) assertCursorInBounds();
      }
    }

    /**
     * @return {@code true} if we haven't advanced beyond the end of the region list
     */
    public boolean isValid() {
      return !region.isZero();
    }

    /**
     * @param ref The object in question
     * @return {@code true} if the object's start address is in this region
     */
    @Inline
    public boolean isInRegion(ObjectReference ref) {
      Address addr = VM.objectModel.refToAddress(ref);
      return addr.GE(BumpPointer.getDataStart(region)) && addr.LE(limit);
    }

    /**
     * Print the cursor - for debugging
     */
    public void print() {
      Log.write(name); Log.write(" cursor:");
      Log.write(" region="); Log.write(region);
      Log.write(" limit="); Log.write(limit);
      Log.write(" cursor="); Log.write(cursor);
      Log.writeln();

    }
  }
  
//  private static boolean VERBOSE = Options.verbose.getValue() >= 5;
//  private static boolean VERY_VERBOSE = VERBOSE && Options.verbose.getValue() >= 9;
  private static boolean VERBOSE = true;
  private static boolean VERY_VERBOSE = VERBOSE && true;
  
  
  private TransitiveClosure referenceFixer;
  private RegionCursor cursor;
  
  public CopyingScanner(CompressorCollector compressorCollector) {
    referenceFixer = new ReferenceFixer(compressorCollector);
    cursor = new CopyingScanner.RegionCursor("to");
  }
  
  @Override
  public void scan(ObjectReference object) {
    if(VM.VERIFY_ASSERTIONS) VM.assertions._assert(cursor.isInit());
    
    if(VERY_VERBOSE) {
      Log.write("[CopyingScanner] scan: "); Log.write(object); Log.writeln();
    }
    
    if(VM.VERIFY_ASSERTIONS) {
      if(!MarkCopySpace.getForwardingPointer(object).isNull()) {
        VM.assertions._assert(!Compressor.fromSpace().isLive(object));
      }
      if(Compressor.fromSpace().isLive(object)) {
        VM.assertions._assert(MarkCopySpace.getForwardingPointer(object).isNull());
      }
      
    }
    
    if(!MarkCopySpace.getForwardingPointer(object).isNull())
    {
      ObjectReference newObj = MarkCopySpace.getForwardingPointer(object);
      // Fix object references
      if(VERBOSE) {
        Log.write("[CopyingScanner] Fixing PRECOPIED refs for "); Log.write(newObj); Log.writeln();
        Log.write("Old object "); Log.write(object); Log.writeln();
      }
      VM.scanning.scanObject(referenceFixer, newObj);
      // No need to copy anything as this object was precopied during marking
    }
    else if (Compressor.fromSpace().isLive(object)) {
      // Fix object references
      if(VERBOSE) {
        Log.write("[CopyingScanner] Fixing refs for "); Log.write(object); Log.writeln();
      }
      VM.scanning.scanObject(referenceFixer, object);
      
      // Copy the object:
      // 1. Find place to copy to using the cursor
      if(!cursor.isAvailable(VM.objectModel.getCurrentSize(object))) {
        cursor.advanceToNextRegion();
        if(VM.VERIFY_ASSERTIONS) VM.assertions._assert(cursor.isValid());
      }
      
      int size = VM.objectModel.getSizeWhenCopied(object);
      int align = VM.objectModel.getAlignWhenCopied(object);
      int offset = VM.objectModel.getAlignOffsetWhenCopied(object);
      
      if(VERBOSE) {
        Log.write("[CopyingScanner] Original cursor = "); Log.write(cursor.get()); Log.writeln();
        Log.write("[CopyingScanner] Aligned Address = "); 
        Log.write(Allocator.alignAllocationNoFill(cursor.get(), align, offset)); Log.writeln();
      }
      
      // Set the cursor on the aligned start of the next object
      cursor.incTo(Allocator.alignAllocationNoFill(cursor.get(), align, offset));
      
      if (!cursor.isAvailable(size)) {
        // We preallocated enough space so we know we will have a next region
        cursor.advanceToNextRegion();
        // Set the cursor on the aligned start of the next object in the new region
        cursor.incTo(Allocator.alignAllocationNoFill(cursor.get(), align, offset));
      }

      ObjectReference to = VM.objectModel.getReferenceWhenCopiedTo(object, cursor.get());
      
      // Perform the copying
      Address newObject = VM.objectModel.copyTo(object, to, to.toAddress());     
      
      // Move the cursor by the size of the object
      cursor.inc(size);

      if (VERY_VERBOSE) {
        Log.write("C["); Log.write(object); Log.write("/");
        Log.write(Space.getSpaceForObject(object).getName()); Log.write("] -> ");
        Log.write(newObject); Log.write("/");
        Log.write(Space.getSpaceForObject(newObject.toObjectReference()).getName());
        Log.writeln("]");
      }
    }
  }

  @Override
  public void init(Address current) {
    Address block = Block.align(current);
    Log.write("Init CopyingScanner "); Log.write(block); Log.writeln();
    cursor.init(block, current);
  }

}
